#include "mkvPlayer.hpp"
#include "PlayFinishException.hpp"
#include "gst/gstbus.h"
#include <sstream>
#include <stdexcept>
#include <string>
#include <spdlog/spdlog.h>

void pad_added_handler (GstElement *src, GstPad *pad, gpointer data)
{
  GstPad *sink_pad = gst_element_get_static_pad(((MKVPlayer*)data)->h264parse, "sink");
  GstPadLinkReturn ret;
  GstCaps *new_pad_caps = NULL;
  GstStructure *new_pad_struct = NULL;
  const gchar *new_pad_type = NULL;

  g_print ("Received new pad '%s' from '%s':\n", GST_PAD_NAME (pad), GST_ELEMENT_NAME (src));

  /* If our converter is already linked, we have nothing to do here */
  if (gst_pad_is_linked (sink_pad)) {
    g_print ("We are already linked. Ignoring.\n");
    goto exit;
  }

  /* Check the new pad's type */
  new_pad_caps = gst_pad_get_current_caps (pad);
  new_pad_struct = gst_caps_get_structure (new_pad_caps, 0);
  new_pad_type = gst_structure_get_name (new_pad_struct);
  if (!g_str_has_prefix (new_pad_type, "video/x-h264")) {
    g_print ("It has type '%s' which is not raw h264. Ignoring.\n", new_pad_type);
    goto exit;
  }

  /* Attempt the link */
  ret = gst_pad_link (pad, sink_pad);
  if (GST_PAD_LINK_FAILED (ret)) {
    g_print ("Type is '%s' but link failed.\n", new_pad_type);
  } else {
    g_print ("Link succeeded (type '%s').\n", new_pad_type);
  }

exit:
  /* Unreference the new pad's caps, if we got them */
  if (new_pad_caps != NULL)
    gst_caps_unref (new_pad_caps);

  /* Unreference the sink pad */
  gst_object_unref (sink_pad);

}

static long object_counter=0;

MKVPlayer::MKVPlayer()
{
  auto CreateOnlyNameA=[](const char* name){
    std::stringstream ss; ss<<name<<"_MKVPlayer_"<<object_counter;
    object_counter++;
    return ss.str();
  };
  filesrc=gst_element_factory_make("filesrc",CreateOnlyNameA("filesrc").c_str());
  matroskademux=gst_element_factory_make("matroskademux",CreateOnlyNameA("matroskademux").c_str());
  h264parse=gst_element_factory_make("h264parse",CreateOnlyNameA("h264parse").c_str());
  nvh264dec=gst_element_factory_make("nvh264dec",CreateOnlyNameA("nvh264dec").c_str());
  cudaconvert=gst_element_factory_make("cudaconvert",CreateOnlyNameA("cudaconvert").c_str());
  cudadownload=gst_element_factory_make("cudadownload",CreateOnlyNameA("cudadownload").c_str());
  appsink=gst_element_factory_make("appsink",CreateOnlyNameA("appsink").c_str());
  pipeline=gst_pipeline_new(CreateOnlyNameA("pipeline").c_str());

  g_object_set (appsink, "max-buffers", 10, nullptr);

  if(!nvh264dec)
  {
    nvh264dec=gst_element_factory_make("vaapih264dec",CreateOnlyNameA("nvh264dec").c_str());
    cudaconvert=gst_element_factory_make("videoconvert",CreateOnlyNameA("cudaconvert").c_str());
    cudadownload=gst_element_factory_make("queue",CreateOnlyNameA("cudadownload").c_str());
  }
  if(!filesrc || !matroskademux || !h264parse || !nvh264dec || !cudaconvert || !cudadownload || !appsink || !pipeline)
    throw std::runtime_error("无法创建所有Gstreamer元素");

  gst_bin_add_many(GST_BIN(pipeline),filesrc,matroskademux,h264parse,nvh264dec,cudaconvert,cudadownload,appsink,nullptr);
  GstCaps* caps=gst_caps_new_simple("video/x-raw",
                                    "width",G_TYPE_INT,1920,
                                    "height",G_TYPE_INT,1080,
                                    "framerate",GST_TYPE_FRACTION,30,1,
                                    "format",G_TYPE_STRING,"RGB",
                                    nullptr);
  if(!gst_element_link_filtered(cudadownload,appsink,caps))
  {
    throw std::runtime_error("Can't link cudadownload to appsink use this caps");
  }
  gst_caps_unref(caps);
  if(!gst_element_link_many(filesrc,matroskademux,nullptr))
  {
    throw std::runtime_error("Can't link filesrc to matroskademux use this caps");
  }
  if(!gst_element_link_many(h264parse,nvh264dec,cudaconvert,cudadownload,nullptr))
  {
    throw std::runtime_error("Can't link element in pipeline");
  }
  g_signal_connect (matroskademux,"pad-added",G_CALLBACK (pad_added_handler),this);
}

MKVPlayer::~MKVPlayer()
{
  Stop();
}

void MKVPlayer::Start(std::filesystem::path file,bool callbackMode)
{
  if(PlayFlag==true)
    throw std::logic_error("MKVPlayer is playing");
  if(!std::filesystem::exists(file))
    throw std::runtime_error(fmt::format("无法打开相应的文件 {}",file.c_str()));
  g_object_set(filesrc,"location",file.c_str(),nullptr);
  auto ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
  if (ret == GST_STATE_CHANGE_FAILURE) {
    spdlog::warn("无法设置播放状态");
    return;
  }
  if(callbackMode)
  {
    ReceiveDataThread=std::thread([this](){
      PlayFlag=true;
      while(true)
      {
        //获取sample，如果没有就会堵塞
        //获得的Sample内部带有互斥锁，是线程安全的
        GstSample* sample = gst_app_sink_pull_sample((GstAppSink*)appsink);
        //如果获得的为空指针，很有可能流被设置为eof
        if(sample==nullptr)
        {
          PlayFlag=false;
          gst_element_set_state (pipeline, GST_STATE_NULL);
          funcStopCallback();
          return;
        }
        //从sample中获得缓存
        GstBuffer* buffer=gst_sample_get_buffer(sample);
        if(buffer!=nullptr)
        {
          //获取buffer中的mem数量
          guint buffer_num=gst_buffer_n_memory(buffer);
          //遍历提取
          for(int i=0;i<buffer_num;i++)
          {
            //获取图像内存和大小
            GstMemory* mem=gst_buffer_peek_memory(buffer,i);
            gsize size=gst_memory_get_sizes(mem,nullptr,nullptr);
            {
              funcNewImageCallback((const char*)mem,1920,1080);
            }
          }
        }
        //释放内存，这里不要释放buffer和memory,都是由sample进行管理的
        gst_sample_unref(sample);
      }
    });
  }
  else
  {
    PlayFlag=true;
  }
}

void MKVPlayer::Stop()
{
  if(PlayFlag==true)
  {
    auto ret = gst_element_set_state (pipeline, GST_STATE_NULL);
    if (ret == GST_STATE_CHANGE_FAILURE) {
      spdlog::warn("无法设置停止状态");
    }
    if(ReceiveDataThread.joinable())
      ReceiveDataThread.join();
    PlayFlag=false;
  }
}

bool MKVPlayer::GetPlayStatus()
{
  return PlayFlag;
}

void MKVPlayer::GetNewImage(char* data)
{
  //获取sample，如果没有就会堵塞
  //获得的Sample内部带有互斥锁，是线程安全的
  GstSample* sample = gst_app_sink_pull_sample((GstAppSink*)appsink);
  //如果获得的为空指针，很有可能流被设置为eof
  if(sample==nullptr)
    throw PlayFinishException();
  //从sample中获得缓存
  GstBuffer* buffer=gst_sample_get_buffer(sample);
  if(buffer!=nullptr)
  {
    //获取buffer中的mem数量
    guint buffer_num=gst_buffer_n_memory(buffer);
    //获取图像内存和大小
    GstMemory* mem=gst_buffer_peek_memory(buffer,0);
    gsize size=gst_memory_get_sizes(mem,nullptr,nullptr);
    memcpy(data,mem,size);
  }
  //释放内存，这里不要释放buffer和memory,都是由sample进行管理的
  gst_sample_unref(sample);
}
